package model.playingfield;

import java.awt.Color;
import java.io.Serializable;
import java.util.ArrayList;

import model.Player;

/**
 * This is the PlayingField. It consists of three areas, each
 * made up of spots: the starting areas, the circuit, and the runways.
 * 
 * It has 4 Starting areas, each consisting of 4 Spots. This
 * is where each players Pieces will be placed at the onset 
 * of the game.
 * 
 * The main Circuit is a set of 40 Spots, leading around in a 
 * connected pattern. 
 * 		* 4 of these Spots are starting points, onto which each player
 * 		  moves his/her Pieces from the starting area to the circuit.
 * 		* 4 of these Spots are entry points to each players Runway.
 * 
 * A players Runway is a set of 4 spots, leading up into the Nest.
 * 
 * The Nest is merely an area out of which Pieces cannot be moved,
 * and needs no logical representation.
 * 
 * Further, there are logical connections between all these three 
 * classes of 
 */
public class PlayingField implements Serializable{
	private static final long serialVersionUID = 1L;

	// Players and their information
	Player[] players;

	// Player pieces and their locations	
	Integer[][] pieceIndex = new Integer[4][4];

	/*
	 * Index over all spots:
	 *   0 - 39  are the spots on the main circuit
	 *   40 - 55 are the spots leading to the nest from a Spot
	 *   56 - 71 are the starting areas
	 */
	private ArrayList<Spot> spotIndex = new ArrayList<Spot>();

	// Index over where each players pieces are at the moment

	public PlayingField(Player[] players) {

		/*
		 * Initiate the circuit:
		 * 	 first initiate the Spots 	   	    (spots 0, 10, 20, 30)
		 *   second initiate entry spots on the circuit (spots 1, 11, 21, 31)
		 *   third initiate the Spots running up to the nest for each player
		 *   fourth initiate the starting area Spots   
		 *   last initiate all other Spots, and tie them together
		 */

		/*
		 * Create initial initialization for all spots. Set them all to white,
		 * without an owner. This will be changed later on.
		 * 
		 * Also, make sure they all point where they should. This is pretty straight
		 * forward.
		 */
		this.players = players;

		for(int i = 0; i < 72; i++)
			spotIndex.add(i, new Spot(Color.WHITE, i));


		for(int i = 0; i < 40; i += 10) {
			spotIndex.remove(i);
			spotIndex.add(i, new BranchingSpot(Color.WHITE, i));
		}

		/*
		 * Tie the circuit spots together (note that the last one, 39, should point
		 * back to the first one, 0.
		 */
		for(int i = 0; i < 39; i++)
			spotIndex.get(i).setNextSpot(spotIndex.get(i+1));
		spotIndex.get(39).setNextSpot(spotIndex.get(0));

		/*
		 * Tie the runway spots together.
		 */
		for(int i = 40; i <= 55; i+=4)
			for(int j = 0; j < 3; j++)
				spotIndex.get(i+j).setNextSpot(spotIndex.get(i+j+1));


		/*
		 * Arrange the playingfield in a way that reflects all the participating 
		 * players. This will use the players ID in order to set stuff up.
		 */

		for(Player player : players) {

			if(player == null) 
				continue;

			/*
			 * Initiate the branching Spots. These spots should have references both
			 * to the next spot on the circuit, as well as the first spot on the 
			 * relevant players runway.
			 * 
			 * After that, make sure that they are pointing where they should be
			 * pointing.
			 * 
			 * A little explanation of the maths: 
			 * 
			 * we find the index of the current players
			 * BranchingSpot by multiplying that players ID with 10. This is because player
			 * 0 has his/her BranchingSpot at 0, player 1 has his/her spot on 10, etc etc.
			 * 
			 * We find the first Spot the BranchingSpot should point to by taking the players
			 * ID, multiplying by 4, and then adding 40. This is because player 0s BranchingSpot
			 * should point to spot 40, for player 1 it should be 44, etc.
			 */
			int ID = player.getID();
			BranchingSpot workingSpot = (BranchingSpot)spotIndex.get(ID*10);

			workingSpot.setOwner(player);
			workingSpot.setNextSpot(spotIndex.get(workingSpot.getIndex() + 1));
			workingSpot.setNextRunway(spotIndex.get( (ID*4) + 40) );

			/*
			 * Initiate the entry Spot to the circuit for the current player. This
			 * is the Spot which the players pieces will first land on when moving out
			 * of the starting area.
			 */
			spotIndex.get( (ID*10 + 1) ).setOwner(player);
			// 0 -> 40, 10 -> 44, 20 -> 48, 30 -> 52
			//WHAT? - mruzuki
			// MYSTERY! - christopher
			/*
			 * Initiate Spots running up to the nest for each player (the runway Spots).
			 * ^This is not true. - mruzuki
			 * ^^ Indeed, I made a small mistake. Fixed now. - christopher
			 */
			int startingIndex = (ID*4) + 56;
			for(int i = 0; i < 4; i++){
				//System.out.println("PlayingField, setting owner at: "+spotIndex.get(startingIndex + i).getIndex()+" to: " +player.getID());
				spotIndex.get(startingIndex + i).setOwner(player);
			}

			/*
			 * Finally, initiate the starting Spots, and put the player pieces into
			 * their starting positions. The starting position of a given piece is of
			 * course in it's respective players starting location.
			 * 
			 * Any spot in a starting location should point to the entry Spot for that
			 * player, that is, the first spot on the circuit a Piece leaving the 
			 * starting area should land on.
			 *	
			 * Along with setting this all up, set up the pieceIndex as well, which
			 * merely contains indexes to where a given players pieces are.
			 */

			int entrySpotIndex = (ID*10) + 1;
			for(int i = 0; i < 4; i++) {

				Spot currentSpot = spotIndex.get(startingIndex + i);

				currentSpot.setOwner(player);
				currentSpot.setOccupant(new Piece(player, i));
				currentSpot.setNextSpot(spotIndex.get(entrySpotIndex));

				pieceIndex[ID][i] = currentSpot.getIndex();
			}
		}
	}

	/**
	 * Move the piece in the target location back to its starting area.
	 * @param location
	 */
	public void moveToStart(int location){

		Spot spot = spotIndex.get(location);
		Piece occupant = spot.getOccupant();

		if(occupant == null)
			return;

		int occupantID = occupant.getID();
		int ownerID = occupant.getOwner().getID();
		int newLocation = (ownerID * 4 + 56 + occupantID); 

		spot.removeOccupant();
		spotIndex.get(newLocation).setOccupant(occupant);

		pieceIndex[ownerID][occupantID] = newLocation;
	};

	/**
	 * Move a given players piece from its current location to a new one.
	 * 
	 * @param player the player whose piece to move
	 * @param index  index at which the piece resides
	 * @param moves  how many moves should be made
	 * @return the new index of the piece
	 */
	public int movePiece(int from, int moves) {

		Piece occupant = spotIndex.get(from).getOccupant();
		Spot currentSpot = spotIndex.get(from);

		/*
		 * Remove the Piece from the spot it currently occupies,
		 * and move it forward for the correct number of moves.
		 * 
		 * If the piece reaches its players runway strip in this
		 * process, then leave the main circuit and enter the 
		 * runway.
		 */

		currentSpot.removeOccupant();
		System.out.println(occupant);

		for(int i = 0; i < moves; i++) {

			// Have we reached a BranchingSpot?
			if(currentSpot instanceof BranchingSpot) { 

				/*
				 * Does it belong to the current player? If yes, enter the runway. 
				 * If not, just keep moving along the circuit.
				 */
				if(currentSpot.getOwner() != null){ //WARNING: SUSPECTED COPY+PASTE FROM ANOTHER METHOD  // LIES!!!1
					if(currentSpot.getOwner().equals(occupant.getOwner())) {
						currentSpot = ((BranchingSpot) currentSpot).getNextRunway(); 
						continue;
					}
				}
			}

			currentSpot = currentSpot.getNextSpot();
			
			/*
			 * if we have reached a Spot that does not exist, simply return -1. This
			 * should only happen if the piece has landed on the nest.
			 */
			if(currentSpot == null) {
				pieceIndex[occupant.getOwner().getID()][occupant.getID()] = -1;
				System.out.println("\nSETTING NEW INDEX OF PIECE TO: " + pieceIndex[occupant.getOwner().getID()][occupant.getID()]);
				return -1;
			}
				
		}

		currentSpot.setOccupant(occupant);

		/*
		 * Update the pieceIndex to contain the new index of the Spot which the Piece
		 * is now located at.
		 */
		pieceIndex[occupant.getOwner().getID()][occupant.getID()] = currentSpot.getIndex();
		System.out.println("\nSETTING NEW INDEX OF PIECE TO: " + pieceIndex[occupant.getOwner().getID()][occupant.getID()]);

		System.out.println("movePiece: " + currentSpot.getIndex());
		return currentSpot.getIndex();
	}

	public Integer[][] getPieceLocations() {

		return pieceIndex;
	}

	/** IMPLEMENTED FOR TEST 
	 * Method to set a player pieces at a position.  
	 * @param player The player 
	 * @param index His locations
	 */
	//	public void setPlayerPieceAtLocation(Player player ,Integer index){
	//		pieceIndex[ID][0] = index; 
	//	}

	/**
	 * Checks if the their is players at field at the moment. 
	 * @return True if the there is players at the field. 
	 */
	public boolean hasPlayerAtField(){
		boolean hasPlayers = false;
		for(int i=0; i < pieceIndex.length-1; i++){
			for(int k=0; k < pieceIndex.length-1; k++){
				if(pieceIndex[i][k] != null){
					hasPlayers = true;
					return hasPlayers;
				}
			}
		}
		return hasPlayers;
	}



	/**
	 * Checks if a given spot is occupied or not.
	 * @param location
	 * @return
	 */
	public boolean isSpotOccupied(Integer location) {
		return spotIndex.get(location).isOccupied();
	}

	public int findDestinationSpot(Integer from, Integer moves) {

		Piece occupant = spotIndex.get(from).getOccupant();
		Spot currentSpot = spotIndex.get(from);

		for(int i = 0; i < moves; i++) {

			// Have we reached a BranchingSpot?
			if(currentSpot instanceof BranchingSpot) { 
				
				if(currentSpot.getOwner() != null){
					if(currentSpot.getOwner().equals(occupant.getOwner())) {
						currentSpot = ((BranchingSpot) currentSpot).getNextRunway(); 
						
						continue;
					}
				}
			}

			currentSpot = currentSpot.getNextSpot();
			if(currentSpot == null)
				return -1;
		}
		
		//This will throw nullpointer if the spot is in a runway
		// should be fixed now - christopher
		System.out.println("NEXT INDEX: "+currentSpot.getIndex());

		return currentSpot.getIndex();
	}

	/**
	 * Returns the occupant of a given Spot. If no occupant is there,
	 * this method should simply return null, and it is the duty of the 
	 * caller to check this result to avoid exceptions.
	 * @param location
	 * @return
	 */
	public Integer whoOccupiesSpot(Integer location) {
		System.out.println("Spot occupied by: "+spotIndex.get(location).getOccupant().getOwner().getID());
		return spotIndex.get(location).getOccupant().getOwner().getID();
	}

	/**
	 * Used for more manual moves, that is, moving one piece from one Spot
	 * directly to another, regardless of any underlying rules.
	 * @param from
	 * @param to
	 */
	public void moveSpecific(int from, int to) {

		Piece occupant = spotIndex.get(from).getOccupant();

		spotIndex.get(from).removeOccupant();
		spotIndex.get(to).setOccupant(occupant);

		pieceIndex[occupant.getOwner().getID()][occupant.getID()] = to;
	}

	public Spot getSpot(int index) {
		return spotIndex.get(index);
	}

	/**
	 * Permanently remove a Piece from the game.
	 * @param location
	 */
	public void removePiece(int location) {

		Spot spot = spotIndex.get(location);
		Piece occupant = spot.getOccupant();

		spot.removeOccupant();

		pieceIndex[occupant.getOwner().getID()][occupant.getID()] = null;
	}
}

